package com.github.dengmin.mysql.config;

import com.github.dengmin.mysql.shiro.ShiroPermissionProperties;
import com.github.dengmin.mysql.shiro.ShiroProperties;
import com.github.dengmin.mysql.shiro.cache.LoginRedisService;
import com.github.dengmin.mysql.shiro.jwt.JwtCredentialsMatcher;
import com.github.dengmin.mysql.shiro.jwt.JwtFilter;
import com.github.dengmin.mysql.shiro.jwt.JwtProperties;
import com.github.dengmin.mysql.shiro.jwt.JwtRealm;
import com.github.dengmin.mysql.shiro.service.ShiroLoginService;
import com.github.dengmin.mysql.utils.IniUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.*;

/**
 * @Author dengmin
 * @Created 2020/8/14 上午9:48
 */
@Configuration
@EnableConfigurationProperties({JwtProperties.class, ShiroProperties.class})
@ConditionalOnProperty(value = {"shiro.enable"}, matchIfMissing = true)
public class ShiroConfiguration {

    private static final String SHIRO_FILTER_NAME = "shiroFilter";
    private static final String JWT_FILTER_NAME = "jwtFilter";

    @Bean
    public CredentialsMatcher credentialsMatcher(){
        return new JwtCredentialsMatcher();
    }

    @Bean
    public JwtRealm jwtRealm(LoginRedisService loginRedisService){
        JwtRealm jwtRealm = new JwtRealm(loginRedisService);
        jwtRealm.setCachingEnabled(false);
        jwtRealm.setCredentialsMatcher(credentialsMatcher());
        return jwtRealm;
    }

    @Bean
    public SessionStorageEvaluator sessionStorageEvaluator(){
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        return sessionStorageEvaluator;
    }

    @Bean
    public DefaultSubjectDAO subjectDAO(){
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
        return defaultSubjectDAO;
    }

    /**
     * 安全管理器配置
     * @return
     */
    @Bean
    public SecurityManager securityManager(LoginRedisService loginRedisService){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(jwtRealm(loginRedisService));
        securityManager.setSubjectDAO(subjectDAO());
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public Authenticator authenticator(LoginRedisService loginRedisService){
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        authenticator.setRealms(Collections.singletonList(jwtRealm(loginRedisService)));
        return authenticator;
    }

    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy(){
        FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName(SHIRO_FILTER_NAME);
        filterRegistrationBean.setFilter(proxy);
        filterRegistrationBean.setAsyncSupported(true);
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
        return filterRegistrationBean;
    }

    @Bean(SHIRO_FILTER_NAME)
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
                                                         ShiroLoginService shiroLoginService,
                                                         LoginRedisService loginRedisService,
                                                         ShiroProperties shiroProperties,
                                                         JwtProperties jwtProperties){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filterMap = getFilterMap(shiroLoginService, loginRedisService, jwtProperties);
        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String, String> filterChainDefinitionMap = getFilterChainDefinitionMap(shiroProperties);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    private Map<String, Filter> getFilterMap(ShiroLoginService shiroLoginService, LoginRedisService loginRedisService, JwtProperties jwtProperties){
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put(JWT_FILTER_NAME, new JwtFilter(shiroLoginService, loginRedisService, jwtProperties));
        return filterMap;
    }

    private Map<String, String> getFilterChainDefinitionMap(ShiroProperties shiroProperties){
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        List<String[]> anonList = shiroProperties.getAnon();
        //设置不需要权限验证的规则
        if(CollectionUtils.isNotEmpty(anonList)){
            anonList.forEach(anonArray -> {
                if(ArrayUtils.isNotEmpty(anonArray)){
                    for(String anonPath : anonArray){
                        filterChainDefinitionMap.put(anonPath, "anon");
                    }
                }
            });
        }
        String definitions = shiroProperties.getFilterChainDefinitions();
        if(StringUtils.isNotBlank(definitions)){
            Map<String,String> section = IniUtil.parseIni(definitions);
            assert section != null;
            for(Map.Entry<String,String> entry: section.entrySet()){
                filterChainDefinitionMap.put(entry.getKey(), entry.getValue());
            }
        }

        //自定义权限路径
        List<ShiroPermissionProperties> permissionProperties = shiroProperties.getPermission();
        if(CollectionUtils.isNotEmpty(permissionProperties)){
            for(ShiroPermissionProperties permission: permissionProperties){
                String url = permission.getUrl();
                String[] urls = permission.getUrls();
                String perm = permission.getPermission();
                if(StringUtils.isBlank(url) && ArrayUtils.isEmpty(urls)){
                    throw new ShiroException("shiro permission config 路径配置不能为空");
                }
                if(StringUtils.isBlank(perm)){
                    throw new ShiroException("shiro permission config permission 路径配置不能为空");
                }
                if(StringUtils.isNotBlank(url)){
                    filterChainDefinitionMap.put(url, perm);
                }
                if(ArrayUtils.isNotEmpty(urls)){
                    for(String uri : urls){
                        filterChainDefinitionMap.put(uri, perm);
                    }
                }
            }
        }

        if(shiroProperties.isEnable()){
            filterChainDefinitionMap.put("/**", JWT_FILTER_NAME);
        }else{
            filterChainDefinitionMap.put("/**", "anon");
        }

        return addDefaultFilterDefinition(filterChainDefinitionMap);
    }

    private Map<String,String> addDefaultFilterDefinition(Map<String,String> filterChainDefinitionMap){
        if(MapUtils.isEmpty(filterChainDefinitionMap)){
            return filterChainDefinitionMap;
        }
        final Map<String,String> map = new LinkedHashMap<>();
        for(Map.Entry<String,String> entry: filterChainDefinitionMap.entrySet()){
            String key = entry.getKey();
            String value = entry.getValue();
            String definition;
            String[] strings = value.split(",");
            List<String> list = new ArrayList<>();
            list.addAll(Arrays.asList(strings));
            definition = String.join(",", list);
            map.put(key, definition);
        }
        return map;
    }


}
